package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointInHorizontalCompartment;
import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.PointOnHorizontalDivider;
import static xj.graph2d.DrawShapeConstants.PointOnVerticalDivider;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_TITLE_LABEL;
import static xj.graph2d.DrawShapeConstants.COMPARTMENT_ROLE_BASE;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.cpady;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.GraphHyperNodeBase;
import xj.graph2d.Label;
import xj.graph2d.graph.Geometry;
import xj.lang.CodeWriter;

public class TitledShape 
  extends GraphHyperNodeBase 
  implements HorizontalCompartmentsShape, VerticalCompartments {

  // title position
  public static final int ABOVE_TOP_LEFT     = 0x0;
  public static final int ABOVE_TOP_RIGHT    = 0x1;
  public static final int UNDER_TOP_LEFT     = 0x2;
  public static final int UNDER_TOP_RIGHT    = 0x3;
  public static final int ABOVE_BOTTOM_LEFT  = 0x4;
  public static final int ABOVE_BOTTOM_RIGHT = 0x5;
  public static final int UNDER_BOTTOM_LEFT  = 0x6;
  public static final int UNDER_BOTTOM_RIGHT = 0x7;

  public TitledShape() {
    this(null, null, ABOVE_TOP_LEFT, 0, 0, 0, 0, false);
  }

  public TitledShape(DrawShape mainShape, DrawShape titleShape,
		     int titlePosition, 
		     float x1, float y1, float x2, float y2) {
    this(mainShape, titleShape, titlePosition, x1, y1, x2, y2, false);
  }

  public TitledShape(DrawShape mainShape, DrawShape titleShape,
		     int titlePosition, 
		     float x1, float y1, float x2, float y2,
		     boolean createComp) {
    super(createComp);
    setShape(mainShape, titleShape, titlePosition, x1, y1, x2, y2);
  }

  @Override
  public DrawShape makeInstance() 
    throws CloneNotSupportedException {
    return new TitledShape((mainShape != null) ? (DrawShape) mainShape.clone() : mainShape, 
			   (titleShape != null) ? (DrawShape) titleShape.clone() : titleShape, 
			   titlePosition, 
			   (float) p1.getX(), (float) p1.getY(),
			   (float) p2.getX(), (float) p2.getY(), 
			   comp != null);
  }

  protected void setShape(DrawShape mainShape, DrawShape titleShape,
			  int titlePosition, 
			  float x1, float y1, float x2, float y2) {
    this.mainShape = mainShape;
    this.titleShape = titleShape;
    this.titlePosition = titlePosition;
    if (mainShape != null) { 
      mainShape.setMovable(false);
      mainShape.setAtomic(true);
    }
    if (titleShape != null) { 
      titleShape.setMovable(false);
      titleShape.setAtomic(true);
    }
    setEnds(x1, y1, x2, y2);
  }     							   

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof TitledShape) {
      TitledShape ts = (TitledShape) s;
      ts.titleMinimumHeight = titleMinimumHeight;
      ts.titleMinimumWidth = titleMinimumWidth;
      ts.titleMaximumHeight = titleMaximumHeight;
      ts.titleMaximumWidth = titleMaximumWidth;
    }
  }

  public void setTitleShape(DrawShape titleShape) {
    this.titleShape = titleShape;
  }

  public DrawShape getTitleShape() {
    return titleShape;
  }

  public void setMainShape(DrawShape mainShape) {
    this.mainShape = mainShape;
  }

  public DrawShape getMainShape() {
    return mainShape;
  }

  public void setTitlePosition(int titlePosition) {
    this.titlePosition = titlePosition;
  }

  public int getTitlePosition() {
    return titlePosition;
  }

  public void setMinimumTitleSize(float titleMinimumHeight,
				  float titleMinimumWidth) {
    this.titleMinimumHeight = titleMinimumHeight;
    this.titleMinimumWidth = titleMinimumWidth;
  }

  public void setMaximumTitleSize(float titleMaximumHeight,
				  float titleMaximumWidth) {
    this.titleMaximumHeight = titleMaximumHeight;
    this.titleMaximumWidth = titleMaximumWidth;
  }

  @Override
  public void setFilled(boolean filled) {
    super.setFilled(filled);
    if (mainShape != null) {
      mainShape.setFilled(filled);
    }
    if (titleShape != null) {
      titleShape.setFilled(filled);
    }
  }

  @Override
  public void setDrawAttr(DrawAttr attr) {
    super.setDrawAttr(attr);
    if (mainShape != null) {
      mainShape.setDrawAttr(attr);
    }
    if (titleShape != null) {
      titleShape.setDrawAttr(attr);
    }
  }

  @Override
  public String getName() {
    return "TitledShape";
  }

  public boolean isExpandable() {
    return false;
  }

  public void setExpandable(boolean b) {
  }

  public boolean isCollapsable() {
    return false;
  }

  public void setCollapsable(boolean b) {
  }

  @Override
  public Shape getOutlineShape() {
    if (titlePosition == UNDER_TOP_LEFT ||
	titlePosition == UNDER_TOP_RIGHT ||
	titlePosition == ABOVE_BOTTOM_LEFT ||
	titlePosition == ABOVE_BOTTOM_RIGHT) { 
      return mainShape.getOutlineShape();
    } else if (titlePosition == ABOVE_TOP_LEFT || 
	       titlePosition == ABOVE_TOP_RIGHT ||
	       titlePosition == UNDER_BOTTOM_LEFT ||
	       titlePosition == UNDER_BOTTOM_RIGHT) { 
      GeneralPath path = new GeneralPath();
      path.append(mainShape.getOutlineShape(), false);
      path.append(titleShape.getOutlineShape(), false);
      return path;
    } else { 
      return super.getOutlineShape();
    }
  }

  @Override
  public void drawFrameEx(Graphics2D g2, float marksize, 
			  boolean fill, Color frameColor) {
    if (titleShape != null) {
      float x1 = (float) titleShape.getEnd1().getX();
      float y1 = (float) titleShape.getEnd1().getY();
      float th = titleShape.getShapeHeight();
      float tw = titleShape.getShapeWidth();
      float mx1, my1, mx2, my2;
      if ((titlePosition & 0x4) != 0) {
	// bottom
	my1 = y1;
	my2 = y1 + th / 2;
      } else {
	// top
	my1 = y1 + th;
	my2 = y1 + th / 2;
      }
      if ((titlePosition & 0x1) != 0) {
	// right
	mx1 = x1 + tw / 2;
	mx2 = x1;
      } else {
	// left
	mx1 = x1 + tw / 2;
	mx2 = x1 + tw;
      }
      g2.setColor(frameColor);
      float md = marksize / 2.0f;
      Rectangle2D r1 = new Rectangle2D.Float(mx1 - md, my1 - md, marksize, marksize);
      Rectangle2D r2 = new Rectangle2D.Float(mx2 - md, my2 - md, marksize, marksize);
      if (fill) { 
	g2.fill(r1);
	g2.fill(r2);
      } else { 
	g2.draw(r1);
	g2.draw(r2);
      }
    }
    if (mainShape instanceof HorizontalCompartments) {
      // HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      ((DrawShapeBase) mainShape).drawFrameEx(g2, marksize, fill, frameColor);
    }
  }

  @Override
  public void move(float dx, float dy) {
    if (mainShape != null) {
      mainShape.move(dx, dy);
    }
    super.move(dx, dy);
  }

  public void adjustHorizontalDividers(float dy, boolean top) {
  }

  @Override
  public void moveCorner(int pos, float dx, float dy, boolean scaleSubshapes) {
    super.moveCorner(pos, dx, dy, scaleSubshapes);
    if (mainShape instanceof HorizontalCompartments && dy != 0) {
      boolean top = (pos == DrawShapeConstants.PointNWCorner || 
		     pos == DrawShapeConstants.PointNECorner);
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      hc.adjustHorizontalDividers(dy, top);
      doLayout();
    }
  }

  @Override
  public void moveEdge(int pos, float dx, float dy, boolean scaleSubshapes) {
    super.moveEdge(pos, dx, dy, scaleSubshapes);
    if (mainShape instanceof HorizontalCompartments && dy != 0) {
      boolean top = (pos == DrawShapeConstants.PointNorthEdge);
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      hc.adjustHorizontalDividers(dy, top);
      doLayout();
    }
  }

  public int getNumberOfHorizontalCompartments() {
    if (mainShape instanceof HorizontalCompartments) {
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      return hc.getNumberOfHorizontalCompartments() + 1;
    } else {
      return 2;
    }
  }

  public Rectangle2D getHorizontalCompartmentBounds(int i) {
    if (i == 0) { 
      return titleShape.getBounds(); 
    } else {
      if (mainShape instanceof HorizontalCompartments) {
	HorizontalCompartments hc = (HorizontalCompartments) mainShape;
	return hc.getHorizontalCompartmentBounds(i - 1);
      } else { 
	if (i == 1) { 
	  return mainShape.getBounds(); 
	}
      }
    }
    return null;
  }

  public Point2D getHorizontalCompartmentCenter(int i) {
    if (i == 0) { 
      return titleShape.getCenterPosition(); 
    } else { 
      if (mainShape instanceof HorizontalCompartments) {
	HorizontalCompartments hc = (HorizontalCompartments) mainShape;
	return hc.getHorizontalCompartmentCenter(i - 1);
      } else { 
	if (i == 1) { 
	  return mainShape.getCenterPosition(); 
	}
      }
    }
    return null;
  }

  public float getHorizontalCompartmentHeight(int i) {
    if (i == 0) { 
      return titleShape.getShapeHeight(); 
    } else {       
      if (mainShape instanceof HorizontalCompartments) {
	HorizontalCompartments hc = (HorizontalCompartments) mainShape;
	return hc.getHorizontalCompartmentHeight(i - 1);
      } else { 
	if (i == 1) { 
	  return mainShape.getShapeHeight(); 
	}
      }
    }
    return 0;
  }

  public float getHorizontalCompartmentTop(int i) {
    if (i > 0) { 
      float h0 = titleShape.getShapeHeight(); 
      if (mainShape instanceof HorizontalCompartments) {
	HorizontalCompartments hc = (HorizontalCompartments) mainShape;
	return hc.getHorizontalCompartmentHeight(i - 1) + h0;
      } else { 
	if (i == 1) { 
	  return h0; 
	}
      }
    }
    return 0;
  }

  public boolean isNumberOfHorizontalCompartmentsFixed() {
    if (mainShape instanceof HorizontalCompartments) {
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      return hc.isNumberOfHorizontalCompartmentsFixed();
    } else {
      return true;
    }
  }

  public void setNumberOfHorizontalCompartmentsFixed(boolean b) {
    if (mainShape instanceof HorizontalCompartments) {
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      hc.setNumberOfHorizontalCompartmentsFixed(b);
    }
  }

  // adjust dividers 
  public void fitCompartments() {
    fitCompartments(true); 
  }

  // adjust dividers 
  public void fitCompartments(boolean keepWidth) {
    if (mainShape instanceof HorizontalCompartmentsShape) {
      HorizontalCompartmentsShape hc = (HorizontalCompartmentsShape) mainShape;
      hc.fitCompartments();
    }
  }

  public DrawShape getShapeInCompartment(int i) {
    if (i == 0) { 
      return titleShape; 
    } 
    if (mainShape instanceof HorizontalCompartmentsShape) {
      HorizontalCompartmentsShape hc = (HorizontalCompartmentsShape) mainShape;
      return hc.getShapeInCompartment(i - 1); 
    } else { 
      if (i == 1)  {
	return mainShape; 
      } 
    }
    return null;
  }

  public DrawShape removeShapeInCompartment(int i) {
    if (i == 0) { 
      //return titleShape; 
    } 
    if (mainShape instanceof HorizontalCompartmentsShape) {
      HorizontalCompartmentsShape hc = (HorizontalCompartmentsShape) mainShape;
      return hc.removeShapeInCompartment(i - 1); 
    } else { 

    }
    return null;
  }

  // shape is not ncessisarily a subshape 
  public void positionShapeInCompartment(DrawShape shape, int i) {
    if (i == 0) { 
      // titleShape; 
    } else if (i > 0) {
      if (mainShape instanceof HorizontalCompartmentsShape) {
	HorizontalCompartmentsShape hc = (HorizontalCompartmentsShape) mainShape;
	hc.positionShapeInCompartment(shape, i - 1);
      }
    }
  }

  public void setShapeInCompartment(DrawShape shape, int i) {
    if (i == 0) { 
      // titleShape; 
    } else if (i > 0) {
      if (mainShape instanceof HorizontalCompartmentsShape) {
	HorizontalCompartmentsShape hc = (HorizontalCompartmentsShape) mainShape;
	int role = COMPARTMENT_ROLE_BASE + i;
	DrawShape s0 = findInRole(role);
	if (s0 != null) {
	  removeShape(s0);
	}
	if (shape != null) {
	  shape.setRole(role);
	  addShape(shape);
	  hc.positionShapeInCompartment(shape, i - 1);
	}
	//hc.setShapeInCompartment(shape, i - 1); 
      } else { 
	
      }
    }
  }

  public boolean isScaleTopCompartment() {
    return false;
  }

  public void setScaleTopCompartment(boolean scaleTopCompartment) {
  }

  // fix
  public void setHorizontalDividers(float[] dividers) {
  }

  // fix
  public float[] getHorizontalDividers() {
    return null;
  }

  public void addHorizontalDivider(int p, float dy) {
    if (p > 0 && 
	mainShape instanceof HorizontalCompartments) {
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      hc.addHorizontalDivider(p - 1, dy);
    }
  }

  public void removeHorizontalDivider(int p) {
    if (p > 0 && 
	mainShape instanceof HorizontalCompartments) {
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      hc.removeHorizontalDivider(p - 1);
    }
  }

  public void moveHorizontalDivider(int i, float dy) {
    if (i == 0) {
      if (titleShape != null) {
	float x1 = (float) titleShape.getEnd1().getX();
	float y1 = (float) titleShape.getEnd1().getY();
	float th = titleShape.getShapeHeight();
	float tw = titleShape.getShapeWidth();
	if ((titlePosition & 0x4) != 0) {
	  // bottom
	  th -= dy;
	  titleShape.setEnds(x1, y1 - dy, x1 + tw, y1 + th);
	} else {
	  // top
	  th += dy;
	  titleShape.setEnds(x1, y1, x1 + tw, y1 + th);
	}
	calculateBounds();
      }
    } else if (mainShape instanceof HorizontalCompartments) {
      HorizontalCompartments hc = (HorizontalCompartments) mainShape;
      hc.moveHorizontalDivider(i - 1, dy);
    }
  }

  public void moveVerticalDivider(int i, float dx) {
    if (titleShape != null) {
      float x1 = (float) titleShape.getEnd1().getX();
      float y1 = (float) titleShape.getEnd1().getY();
      float th = titleShape.getShapeHeight();
      float tw = titleShape.getShapeWidth();
      if ((titlePosition & 0x1) != 0) {
	// right
	tw -= dx;
	titleShape.setEnds(x1 - dx, y1, x1 + tw, y1 + th);
      } else {
	// left
	tw += dx;
	titleShape.setEnds(x1, y1, x1 + tw, y1 + th);
      }
      calculateBounds();
    }
  }

  public void setVerticalDividers(float[] dividers) {
  }

  public float[] getVerticalDividers() {
    return null;
  }

  public int getNumberOfVerticalCompartments() {
    return 2;
  }

  public int inVerticalCompartment(DrawShape s) {
    if (s != null) {
      Point2D c = s.getCenterPosition();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      if (cx >= x - d && cx <= x + w + d && cy >= y - d && cy <= y + h + d) {
	if (titleShape != null) {
	  float x1 = (float) titleShape.getEnd1().getX();
	  float tw = titleShape.getShapeWidth();
	  if (cx > x1 + tw) {
	    return 1;
	  }
	}
	return 0;
      }
    }
    return -1;
  }

  public boolean isNumberOfVerticalCompartmentsFixed() {
    return true;
  }

  public void addVerticalDivider(int p, float dy) {
  }

  public void removeVerticalDivider(int p) {
  }

  public int inHorizontalCompartment(DrawShape s) {
    if (s != null) {
      Point2D c = s.getCenterPosition();
      float cx = (float) c.getX();
      float cy = (float) c.getY();
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      if (cx >= x - d && 
	  cx <= x + w + d && 
	  cy >= y - d && 
	  cy <= y + h + d) {
	if (cy < y + hTitleDivider) {
	  return 0;
	} else {
	  if (mainShape instanceof HorizontalCompartments) {
	    HorizontalCompartments hc = (HorizontalCompartments) mainShape;
	    return hc.inHorizontalCompartment(s) + 1;
	  } else {
	    return 1;
	  }
	}
      }
    }
    return -1;
  }

  public void setDividerStyle(DividerLineMode lineMode, int lineStyle) {} 
  public void setHighLightCompartments(boolean highlight) {} 

  @Override
  public int isInside(float px, float py) {
    int result = super.isInside(px, py);
    if (result == PointInside) {
      if (titleShape != null) {
	float x1 = (float) Math.min(p1.getX(), p2.getX());
	float dx = Math.abs(x1 + vTitleDivider - px);
	float ty1 = (float) titleShape.getEnd1().getY();
	float ty2 = (float) titleShape.getEnd2().getY();
	float tymin = Math.min(ty1, ty2);
	float tymax = Math.max(ty1, ty2);
	if (dx <= d && py >= tymin && py <= tymax) {
	  result = PointOnVerticalDivider;
	} else {
	  float y1 = (float) Math.min(p1.getY(), p2.getY());
	  float dy = Math.abs(y1 + hTitleDivider - py);
	  if (dy <= d) {
	    result = PointOnHorizontalDivider;
	  } else if (mainShape instanceof HorizontalCompartments) {
	    int pos = mainShape.isInside(px, py);
	    int pos1 = pos & DrawShapeConstants.HIGH_MASK;
	    if (pos1 == PointOnHorizontalDivider || 
		pos1 == PointInHorizontalCompartment) {
	      result = pos + 1;
	    }
	  }
	}
      }
    }
    return result;
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    float w0 = (float) Math.abs(p1.getX() - p2.getX());
    float h0 = (float) Math.abs(p1.getY() - p2.getY());
    float w1 = Math.abs(x1 - x2);
    float h1 = Math.abs(y1 - y2);
    float sx = w1 / w0;
    float sy = h1 / h0;
    if (mainShape != null) {
      mainShape.scale(sx, sy);
    }
    if (titleShape != null) {
      titleShape.scale(sx, sy);
    }
    super.setEndsAdj(x1, y1, x2, y2);
    positionTitle();
  }

  @Override
  public void draw(Graphics2D g2) {
    if (mainShape != null) {
      mainShape.draw(g2);
    }
    if (titleShape != null) {
      titleShape.draw(g2);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2) {
    if (mainShape != null) {
      mainShape.drawOutline(g2);
      mainShape.drawSubShapesOutline(g2);
    }
    if (titleShape != null) {
      titleShape.drawOutline(g2);
    }
    drawSubShapesOutline(g2);
  }

  @Override
  public void drawSubShapes(Graphics2D g2, int mode, 
			    DrawShape selectedShape,
			    Set<DrawShape> selectionSet) {
    super.drawSubShapes(g2, mode, selectedShape, selectionSet);
    if (mainShape != null) {
      mainShape.drawSubShapes(g2, mode, selectedShape, selectionSet);
    }
  }

  @Override
  public void moveEnd1(float dx, float dy) {
    super.moveEnd1(dx, dy); 
    calculateBounds();
  }

  @Override
  public void moveEnd2(float dx, float dy) {
    super.moveEnd2(dx, dy); 
    calculateBounds();
  }

  protected void positionTitle() {
    if (mainShape != null) {
      float x1 = (float) Math.min(p1.getX(), p2.getX());
      float y1 = (float) Math.min(p1.getY(), p2.getY());
      float x2 = (float) Math.max(p1.getX(), p2.getX());
      float y2 = (float) Math.max(p1.getY(), p2.getY());
      float h = y2 - y1;
      float w = x2 - x1;
      float offset = 0;

      if (mainShape instanceof RoundRectangleShape) {
	RoundRectangleShape r = (RoundRectangleShape) mainShape;
	offset = r.getArcWidth() / 2;
      }

      if (titleShape != null) {
	float th = titleShape.getShapeHeight();
	float tw = titleShape.getShapeWidth();
	float minTh = titleMinimumHeight;
	float maxTh = titleMaximumHeight * h;
	float minTw = titleMinimumWidth;
	float maxTw = titleMaximumWidth * w;

	float gap = Math.min(40, tw / 2); 
	if (w < tw + gap) { 
	  w = tw + gap; 
	  x2 = x1 + w;
	}

	if (minTh <= 1 && minTh > 0) {
	  minTh *= h;
	}
	if (maxTh <= 1 && maxTh > 0) {
	  maxTh *= h;
	}
	if (minTw <= 1 && minTw > 0) {
	  minTw *= w;
	}
	if (maxTw <= 1 && maxTw > 0) {
	  maxTw *= w;
	}

	if (th < minTh) {
	  th = minTh;
	}
	if (th > maxTh) {
	  th = maxTh;
	}
	if (tw < minTw) {
	  tw = minTw;
	}
	if (tw > maxTw) {
	  tw = maxTw;
	}
	switch (titlePosition) {
	case ABOVE_TOP_LEFT:
	  titleShape.setEnds(x1 + offset, y1, x1 + offset + tw, y1 + th);
	  mainShape.setEnds(x1, y1 + th, x2, y2);
	  hTitleDivider = th;
	  vTitleDivider = tw + offset;
	  break;

	case ABOVE_TOP_RIGHT:
	  titleShape.setEnds(x2 - offset - tw, y1, x2 - offset, y1 + th);
	  mainShape.setEnds(x1, y1 + th, x2, y2);
	  hTitleDivider = th;
	  vTitleDivider = w - tw - offset;
	  break;

	case UNDER_TOP_LEFT:
	  titleShape.setEnds(x1 + offset, y1, x1 + offset + tw, y1 + th);
	  mainShape.setEnds(x1, y1, x2, y2);
	  hTitleDivider = th;
	  vTitleDivider = tw + offset;
	  break;

	case UNDER_TOP_RIGHT:
	  titleShape.setEnds(x2 - offset - tw, y1, x2 - offset, y1 + th);
	  mainShape.setEnds(x1, y1, x2, y2);
	  hTitleDivider = th;
	  vTitleDivider = w - tw - offset;
	  break;

	case ABOVE_BOTTOM_LEFT:
	  titleShape.setEnds(x1 + offset, y2 - th, x1 + offset + tw, y2);
	  mainShape.setEnds(x1, y1, x2, y2);
	  hTitleDivider = h - th;
	  vTitleDivider = tw + offset;
	  break;

	case ABOVE_BOTTOM_RIGHT:
	  titleShape.setEnds(x2 - offset - tw, y2 - th, x2 - offset, y2);
	  mainShape.setEnds(x1, y1, x2, y2);
	  hTitleDivider = h - th;
	  vTitleDivider = w - tw - offset;
	  break;

	case UNDER_BOTTOM_LEFT:
	  titleShape.setEnds(x1 + offset, y2 - th, x1 + offset + tw, y2);
	  mainShape.setEnds(x1, y1, x2, y2 - th);
	  hTitleDivider = h - th;
	  vTitleDivider = tw + offset;
	  break;

	case UNDER_BOTTOM_RIGHT:
	  titleShape.setEnds(x2 - offset - tw, y2 - th, x2 - offset, y2);
	  mainShape.setEnds(x1, y1, x2, y2 - th);
	  hTitleDivider = h - th;
	  vTitleDivider = w - tw - offset;
	  break;
	}
      } else {
	mainShape.setEnds(x1, y1, x2, y2);
      }

      p1.setLocation(x1, y1);
      p2.setLocation(x2, y2);
      setBounds(x1, y1, x2, y2);
      //setEnds(x1, y1, x2, y2);
    }
  }

  @Override
  public void calculateBounds() {
    positionTitle();
    super.calculateBounds();
  }

  @Override
  public void setLabelOffset() {
    Label label = findLabel();
    if (label != null) {
      if (label.getRole() == ROLE_NODE_TITLE_LABEL) {
	setLabelOffset(titleShape.getBounds());
      } else if (label.getRole() == ROLE_NODE_LABEL) {
	setLabelOffset(mainShape.getBounds());
      } else {
	setLabelOffset(bounds);
      }
    }
  }

  @Override
  public void positionLabel() {
    Label label = findLabel();
    if (label != null) {
      if (label.getRole() == ROLE_NODE_TITLE_LABEL) {
	float th0 = titleShape.getShapeHeight();
	float tw0 = titleShape.getShapeWidth();
	float th = label.getShapeHeight() + 2 * cpady;
	float tw = label.getShapeWidth() + 2 * cpady;
	float h = Math.max(th0, th); 
	float w = Math.max(tw0, tw); 
	if ((w != tw0) || (h != th0)) { 
	  titleShape.moveEnd2((w - tw0), (h - th0));
	}
	positionLabel(titleShape.getBounds());
      } else if (label.getRole() == ROLE_NODE_LABEL) {
	positionLabel(mainShape.getBounds());
      } else {
	positionLabel(bounds);
      }
      positionTitle();
    }
  }

  /*
   * Return the intersection point of p and the center of the node The
   * default implementation assumes the node is a rectangle.
   */
  @Override
  public Point2D intersect(Point2D p) {
    return intersect(p, 0, 0);
  }

  @Override
  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      Point2D c = getCenterPosition();
      c.setLocation(c.getX() + offsetX, c.getY() + offsetY);
      if (titlePosition == ABOVE_TOP_LEFT || 
	  titlePosition == ABOVE_TOP_RIGHT || 
	  titlePosition == UNDER_BOTTOM_LEFT || 
	  titlePosition == UNDER_BOTTOM_RIGHT) { 
	// find the intersection closer to p
	Point2D p1 = Geometry.intersectionRectLine(mainShape.getEnd1(),
						     mainShape.getEnd2(), p, c);
	Point2D p2 = Geometry.intersectionRectLine(titleShape.getEnd1(),
						     titleShape.getEnd2(), p, c);
	if (p1 != null && 
	    p2 != null) { 
	  double d1 = Geometry.distance(p, p1);
	  double d2 = Geometry.distance(p, p2);
	  
	  if (isInside(p) == PointOutside) {
	    //System.out.println("TitleShape.intersect() d1=" + d1 + " d2=" + d2);
	    if (d2 < d1) {
	      return p2; 
	    } else { 
	      return p1; 
	    }
	  } else {
	    // p is inside 
	    if (mainShape.isInside(p) == PointOutside) {
	      return p1;
	    } else { 
	      if (d2 < d1) {
		return p1; 
	      } else { 
		return p2; 
	      }
	    }
	    //return Geometry.intersectionRectLine(getULCorner(), getLRCorner(), c, p);
	  }
	}
	 
	if (p1 != null) {
	  //System.out.println("TitleShape.intersect() p1 != null");
	  return p1; 
	} else { 
	  //System.out.println("TitleShape.intersect() p2 != null");
	  return p2;
	}
      } else {
	// title shape is inside main shape 
	return Geometry.intersectionRectLine(getULCorner(), getLRCorner(), p, c);
      }
    } 
    return null;
  }

  /* 
  public DrawShapeSearch.Result searchSubShapes(Point2D p, 
						DrawShape exclude,
						boolean excludeOpenShape) {
    DrawShapeSearch.Result result = super.searchSubShapes(p, exclude, excludeOpenShape);
    if (result == null) { 
      result = mainShape.searchSubShapes(p, exclude, excludeOpenShape);
    }
    return result; 
  }
  */

  @Override
  public void doLayout() {
    if (debug) { 
      System.out.println("TitledShape.doLayout()");
    }

    if (titleShape != null) {
      titleShape.doLayout();
    }
    if (mainShape != null) {
      mainShape.doLayout();
    }

    super.doLayout();
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("pos", titlePosition);
    attrs.put("hdiv", hTitleDivider);
    attrs.put("vdiv", vTitleDivider);
    return attrs;
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
				  String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      String mname = getPrefixedName("MainShape", namespacePrefix);
      out.indentInc();
      out.println("<" + mname + ">");
      if (mainShape != null) {
	mainShape.writeXML(out, mode, namespacePrefix);
      }
      out.thisIndentDec();
      out.println("</" + mname + ">");

      String tname = getPrefixedName("TitleShape", namespacePrefix);
      out.indentInc();
      out.println("<" + tname + ">");
      if (titleShape != null) {
	titleShape.writeXML(out, mode, namespacePrefix);
      }
      out.thisIndentDec();
      out.println("</" + tname + ">");
    }
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("pos");
      try {
	titlePosition = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }

      s = node.getAttribute("hdiv");
      try {
	hTitleDivider = Float.parseFloat(s);
      } catch (NumberFormatException ex) {
      }

      s = node.getAttribute("vdiv");
      try {
	vTitleDivider = Float.parseFloat(s);
      } catch (NumberFormatException ex) {
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String name = e.getLocalName();
      if ("MainShape".equals(name) || "TitleShape".equals(name)) {
	org.w3c.dom.NodeList snodes = e.getChildNodes();
	if (snodes != null) {
	  int sn = snodes.getLength();
	  for (int j = 0; j < sn; j++) {
	    org.w3c.dom.Node snode = snodes.item(j);
	    String uri = snode.getNamespaceURI();
	    if (snode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE && 
		(namespaceURI == null || namespaceURI.equals(uri))) {
	      /*
	       * DrawShape shape =
	       * DrawShape.makeShapeFromXML((org.w3c.dom.Element) snode,
	       * namespaceURI); if ("MainShape".equals(name)) { mainShape =
	       * shape; } else { titleShape = shape; }
	       */

	      if ("MainShape".equals(name)) {
		if (mainShape == null) {
		  mainShape = DrawShapeBase.makeShapeFromXML(
		      (org.w3c.dom.Element) snode, namespaceURI);
		} else {
		  mainShape.setAttrFromXML((org.w3c.dom.Element) snode,
		      namespaceURI);
		}
	      } else {
		if (titleShape == null) {
		  titleShape = DrawShapeBase.makeShapeFromXML(
		      (org.w3c.dom.Element) snode, namespaceURI);
		} else {
		  titleShape.setAttrFromXML((org.w3c.dom.Element) snode,
		      namespaceURI);
		}
	      }
	    }
	  }
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  // mainShape and titleShape are delegates, not subshapes 
  protected DrawShape mainShape;

  protected DrawShape titleShape;

  protected int titlePosition;

  protected float hTitleDivider;

  protected float vTitleDivider;

  // convention for title width and height
  // w/h > 1 : absolute
  // 0 < w/h <= 1: relative to the enclosing shape w/h

  protected float titleMinimumHeight = 4;

  protected float titleMaximumHeight = 0.4f;

  protected float titleMinimumWidth = 4;

  protected float titleMaximumWidth = 0.9f;

}
